#include "tcpsocket.h"

TcpSocket::TcpSocket(QTcpSocket *client, ClientInfo *infoWidget, QListWidget *listWidget)
    : client(client), infoWidget(infoWidget), listWidget(listWidget)
{
    connect(client, &QTcpSocket::readyRead, this, &TcpSocket::onReadyRead);
    connect(client, &QTcpSocket::bytesWritten, this, &TcpSocket::onBytesWritten);
    connect(client, SIGNAL(disconnected()), this, SIGNAL(disconnected()));
}
TcpSocket::~TcpSocket() {
    for(SocketQueue *sq : mSqlist)
        delete sq;
    for(QCryptographicHash *hash : mMapFileChecksum)
        delete hash;
}

void TcpSocket::writeFileList(const QString &path) {
    QByteArray xml;
    // 将FileList信息写入xml
    QXmlStreamWriter xmlWriter(&xml);
    xmlWriter.writeStartDocument();
    xmlWriter.writeStartElement("FileList");
    xmlWriter.writeAttribute("Path", path);
    // 如果 path 为空，则为主列表
    //  传输所有盘符、用户文件夹和桌面
    // 如果 path 不为空，则为特定目录
    //  传输该目录下的内容
    if(path == "") {
        // 遍历所有盘符并写入XML
        for(const QFileInfo &info : QDir::drives())
            xmlWriter.writeTextElement("Driver", info.absoluteFilePath());
        // 将用户文件夹写入XML
        xmlWriter.writeTextElement("Home", QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
        // 将桌面文件夹写入XML
        xmlWriter.writeTextElement("Desktop", QStandardPaths::writableLocation(QStandardPaths::DesktopLocation));
    } else {
        // 得到目录下所有的文件夹并写入XML
        QStringList list = QDir(path).entryList(QDir::Dirs | QDir::NoDotAndDotDot);
        for(const QString &dir : qAsConst(list))
            xmlWriter.writeTextElement("Dir", dir);
        // 得到目录下所有的文件并写入XML
        list = QDir(path).entryList(QDir::Files);
        for(const QString &file : qAsConst(list))
            xmlWriter.writeTextElement("File", file);
    }
    xmlWriter.writeEndElement();
    xmlWriter.writeEndDocument();

    addSocketQueue(new SocketQueue_NormalMsg(xml));
}
void TcpSocket::writeSendFileRequest(const QString &serverPath, const QString &clientPath) {
    QByteArray xml;
    // 将SendFileRequest信息写入xml
    QXmlStreamWriter xmlWriter(&xml);
    xmlWriter.writeStartDocument();
    xmlWriter.writeStartElement("SendFileRequest");
    xmlWriter.writeAttribute("ServerPath", serverPath);
    xmlWriter.writeAttribute("ClientPath", clientPath);
    xmlWriter.writeEndElement();
    xmlWriter.writeEndDocument();

    addSocketQueue(new SocketQueue_NormalMsg(xml));
}
void TcpSocket::parseReceiveMsg(const QByteArray &array) {
    ReceiveData data = parseReceiveData(array);

    // 解析xml
    QDomDocument dom;
    if(!dom.setContent(data.xml))
        return;
    QDomElement root = dom.documentElement();
    // 如果消息为DirRequest，则将文件列表传输回客户端
    //  若 sp 为 Parent，则变为父目录
    // 如果消息为PreSendFile，则对每个文件发送SendFileRequest消息
    // 如果消息为SendFile，则接收数据
    if(root.tagName() == "DirRequest") {
        QString path = root.attribute("Path");
        QString sp = root.attribute("Sp");
        if(sp == "Parent") {
            QFileInfo info(path);
            path = info.isRoot() ? "" : info.path();
        }
        writeFileList(path);
    } else if(root.tagName() == "PreSendFile") {
        QString path = root.attribute("Path");
        QDir dir(path);
        if(!path.isEmpty() && dir.exists()) {
            // 遍历所有节点
            QDomNode node = root.firstChild();
            while(!node.isNull()) {
                QDomElement elem = node.toElement();
                if(!elem.isNull() && elem.nodeName() == "File") {
                    QString filePath = elem.text();
                    QString fileName = QFileInfo(filePath).fileName();
                    if(!fileName.isEmpty()) {
                        // 判断文件是否重复，并得到不重复的第一个序号
                        QFileInfo info(fileName);
                        QString baseName = info.completeBaseName(), suffix = info.suffix();
                        if(!suffix.isEmpty())
                            suffix.insert(0, '.');
                        QString fileName2 = fileName;
                        int renInd = 2;
                        while(dir.exists(fileName2) || dir.exists(fileName2 + ".tfm_tmp")) {
                            fileName2 = baseName + '(' + QString::number(renInd) + ')' + suffix;
                            renInd++;
                        }

                        QString selfFilePath = dir.filePath(fileName2);
                        mMapFileChecksum[selfFilePath] = new QCryptographicHash(QCryptographicHash::Md5);
                        writeSendFileRequest(selfFilePath, filePath);

                        qint64 size = elem.attribute("Size").toLongLong();
                        SendFileWidget *widget = new SendFileWidget("接收中:", filePath, size);
                        QListWidgetItem *item = new QListWidgetItem;
                        widget->adjustSize();
                        item->setSizeHint(QSize(1, widget->height()));
                        item->setFlags(Qt::ItemIsEnabled);
                        listWidget->addItem(item);
                        listWidget->setItemWidget(item, widget);
                        mMapFileWidgetItem[selfFilePath] = FileWidgetItem({ widget, item });
                    }
                }
                node = node.nextSibling();
            }
        }
    } else if(root.tagName() == "SendFile") {
        QString filePath = root.attribute("Path");
        int ind = root.attribute("Ind").toInt();
        if(!filePath.isEmpty() && ind >= 0 && ind < data.dataList.size()) {
            QFile file(filePath + ".tfm_tmp");
            if(file.open(QIODevice::Append)) {
                file.write(data.dataList[ind]);

                if(mMapFileChecksum.contains(filePath))
                    mMapFileChecksum[filePath]->addData(data.dataList[ind]);

                if(mMapFileWidgetItem.contains(filePath)) {
                    SendFileWidget *widget = mMapFileWidgetItem[filePath].widget;
                    widget->setProgress(widget->progress() + data.dataList[ind].length());
                }

                file.close();
            }
        }
    } else if(root.tagName() == "SendFileFinished") {
        QString filePath = root.attribute("Path");
        bool update = root.attribute("Update").toInt();
        QFile file(filePath + ".tfm_tmp");

        QByteArray checksum = QByteArray::fromHex(root.attribute("Checksum").toUtf8());
        QByteArray result;
        if(mMapFileChecksum.contains(filePath)) {
            QCryptographicHash *hash = mMapFileChecksum.take(filePath);
            result = hash->result();
            delete hash;
        }

        bool isRight = checksum == result;

        if(mMapFileWidgetItem.contains(filePath)) {
            FileWidgetItem widgetItem = mMapFileWidgetItem.take(filePath);
            widgetItem.widget->setMsg(isRight ? "校验成功" : "校验失败");
            widgetItem.widget->setProgressBarVisible(false);
            widgetItem.widget->adjustSize();
            widgetItem.item->setSizeHint(QSize(1, widgetItem.widget->height()));
        }

        QByteArray xml;
        QXmlStreamWriter xmlWriter(&xml);
        xmlWriter.writeStartDocument();
        xmlWriter.writeStartElement("ChecksumResult");
        xmlWriter.writeAttribute("Path", filePath);
        xmlWriter.writeCharacters(QString::number(isRight));
        xmlWriter.writeEndElement();
        xmlWriter.writeEndDocument();
        addSocketQueue(new SocketQueue_NormalMsg(xml));

        file.rename(filePath);
        if(update) writeFileList(QFileInfo(filePath).path());
    }
}

void TcpSocket::addSocketQueue(SocketQueue *sq) {
    if(!sq->isVaild())
        return;
    mSqlist << sq;
    if(mSqlist.size() == 1)
        writeFirstSocketQueue();
}
void TcpSocket::writeFirstSocketQueue() {
    if(mSqlist.isEmpty())
        return;
    QByteArray data = mSqlist[0]->data();
    qint64 len = data.length();
    infoWidget->increaseData(8 + data.length());
    client->write(QByteArray((char*)&len, 8) + data);
}

void TcpSocket::onReadyRead() {
    QByteArray array = client->readAll();
    infoWidget->increaseData(array.length());

    mBuffer.append(array);
    if(mBuffer.length() < 8)
        return;
    qint64 len = *reinterpret_cast<qint64*>(mBuffer.data());
    while(len <= mBuffer.length() - 8) {
        parseReceiveMsg(mBuffer.mid(8, (int)len));
        mBuffer = mBuffer.mid(8 + (int)len);
        if(mBuffer.length() < 8)
            break;
        len = *reinterpret_cast<qint64*>(mBuffer.data());
    }
}
void TcpSocket::onBytesWritten() {
    SocketQueue *sq = mSqlist.takeAt(0);
    if(sq->isFinished()) {
        delete sq;
    } else mSqlist << sq;

    writeFirstSocketQueue();
}
